
/**
 * construct a mini instance with the given _loadFlow instance
 * default to mini.flow.DefaultLoadFlow()
 * can be replaced temporarily by setting a mini:flow="${varName}"
 * attribute
 */
define(["mini/flow/DefaultLoadFlow", "mini/flow/Context"], function(DefaultLoadFlow, Context) {
    return function(_loadFlow) {
        var savedTemplates = {};
        var loadFlow = (_loadFlow) ? _loadFlow : new DefaultLoadFlow();
        var elementDataMap = {};//map element=>data instance
        /**
         * return stored template for element with the
         * given id
         */
        function getSavedTemplate(id) {
            return savedTemplates[id];
        }
        /**
         * load model to the specified element
         * the element must have id declared
         */
        function load(element) {
            elementDataMap = {};
            var id = element.getAttribute("id");
            if (!savedTemplates[id]) {
                savedTemplates[id] = element.cloneNode(true);
            }
            //replace current dom object with that kept in savedTemplates
            //this is because current dom object can be changed during
            //the binding process
            var newElement = savedTemplates[id].cloneNode(true);
            var nextSibling = element.nextSibling;
            var parentNode = element.parentNode;
            parentNode.removeChild(element);
            if (nextSibling) {
                parentNode.insertBefore(newElement, nextSibling);
            }
            else {
                parentNode.appendChild(newElement);
            }
            _load(newElement);
        }
        /**
         * internal load procedure
         */
        function _load(element, currentModel, currentObject) {
            if (element == null) {
                return null;
            }
            if (element.getAttribute("mini:model")) {
                currentModel = element.getAttribute("mini:model");
            }
            if (element.getAttribute("mini:object")) {
                currentObject = element.getAttribute("mini:object");
                var object = eval(currentModel + "." + currentObject);
                if (object && object.constructor == Array) {
                    //if the object expression evaluates to an Array, duplicate the template
                    var container = element.parentNode;
                    var firstNewElement = null;
                    for (var i = 0; i < object.length; i++) {
                        var newElement = element.cloneNode(true);
                        if (firstNewElement == null) {
                            firstNewElement = newElement;
                        }
                        //adjust the object expression
                        newElement.setAttribute("mini:object", currentObject + "[" + i + "]");
                        container.appendChild(newElement);
                    }
                    container.removeChild(element);
                    currentObject = currentObject + "[0]";
                    element = firstNewElement;
                }
            }
            if (element == null) {
                return null;
            }
            if (currentObject) {
                elementDataMap[element] = eval(currentModel + "." + currentObject);
            }
            var context = new Context();
            var flowInstance = element.hasAttribute("mini:flow") ? (eval(element.getAttribute("mini:flow"))) : loadFlow;
            context.modelExp = currentModel;
            context.objExp = currentObject;
            context.element = element;
            //alert(context.element+":"+element);
            if (flowInstance.shouldProcess(context)) {
                flowInstance.execute(context);
            }

            if (!flowInstance.shouldProcessChildNodes(context)) {
                return element;
            }
            var child = element.firstChild;
            while (child != null) {
                if (child.nodeType == 1) {
                    //element only
                    child = _load(child, currentModel, currentObject);//child may be replaced
                }
                child = (child != null) ? child.nextSibling : null;
            }
            return element;
        }
        /**
         * return data object for the given element
         */
        function getData(element) {
            return elementDataMap[element];
        }

        this.load = load;
        this.loadFlow = loadFlow;
        this.getData = getData;
        this.getSavedTemplate = getSavedTemplate;
        return this;
    }
});